/// @file PerfOSX.hh
/// @brief Get performance information on OS/X

// *************************************************************************

// Copyright© 2010 Texas A&M University, 
//                 College Station, Texas
//                 Contact: R Crosby <rwc@cs.tamu.edu>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// *************************************************************************

#if defined(__APPLE__)

//#include <mach/task_info.h>
#include <mach/host_info.h>
#include <mach/shared_region.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <unistd.h>
#include <sys/sysctl.h>
#include <sys/resource.h>

#include "PerfBlock.hh"

static inline cpu_type_t cpu_type() {
    cpu_type_t cputype = 0;

    int mib[CTL_MAXNAME];
    size_t len = CTL_MAXNAME;
    if (sysctlnametomib("sysctl.proc_cputype", mib, &len) != -1) {
        mib[len] = getpid();
        len++;

        size_t cputypelen = sizeof(cputype);
        if (sysctl(mib, len, &cputype, &cputypelen, 0, 0) == -1) {
            cputype = 0;
        }
    }

    return cputype;
}

static inline mach_vm_size_t shared_region_size(cpu_type_t cputype) {
    switch(cputype) {
        case CPU_TYPE_POWERPC:
            return SHARED_REGION_SIZE_PPC;
        case CPU_TYPE_POWERPC64:
            return SHARED_REGION_SIZE_PPC64;
        case CPU_TYPE_I386:
            return SHARED_REGION_SIZE_I386;
        case CPU_TYPE_X86_64:
            return SHARED_REGION_SIZE_X86_64;
        default: // unknown CPU type
            return 0;
    }
}

static inline int in_shared_region(cpu_type_t cputype, mach_vm_address_t address) {
    if (cputype == CPU_TYPE_POWERPC &&
        address >= SHARED_REGION_BASE_PPC &&
        address <= (SHARED_REGION_BASE_PPC + SHARED_REGION_SIZE_PPC)) {
        return 1;
    }

    if (cputype == CPU_TYPE_POWERPC64 &&
        address >= SHARED_REGION_BASE_PPC64 &&
        address <= (SHARED_REGION_BASE_PPC64 + SHARED_REGION_SIZE_PPC64)) {
        return 1;
    }
    
    if (cputype == CPU_TYPE_I386 &&
        address >= SHARED_REGION_BASE_I386 &&
        address <= (SHARED_REGION_BASE_I386 + SHARED_REGION_SIZE_I386)) {
        return 1;
    }
    
    if (cputype == CPU_TYPE_X86_64 &&
        address >= SHARED_REGION_BASE_X86_64 &&
        address <= (SHARED_REGION_BASE_X86_64 + SHARED_REGION_SIZE_X86_64)) {
        return 1;
    }
    
    return 0;
}

static int
OSXMemoryUsage ( unsigned long &rprvt,    // Resident private 
		 unsigned long &vprvt,    // Virtual private
		 unsigned long &vsize )   // Virtual private + shared
{
    task_t                    task  = mach_task_self();
    mach_msg_type_number_t    count = TASK_BASIC_INFO_64_COUNT;
    struct task_basic_info_64 taskinfo;

    if ( task_info(task, 
		   TASK_BASIC_INFO_64, 
		   (task_info_t)&taskinfo, 
		   &count) != KERN_SUCCESS ) 
      return 1;

    rprvt = taskinfo.resident_size;
    vsize = vprvt = taskinfo.virtual_size;

    mach_vm_size_t	empty = 0;
    
    cpu_type_t          cputype = cpu_type();

    mach_vm_address_t   address;
    int                 has_shared_regions;
    mach_vm_size_t      size;

    // Go through all the vm regions and check to see if we should count them in the vsize or not
    for (address = 0, has_shared_regions = 0; ; address += size) {

        count = VM_REGION_TOP_INFO_COUNT;

        vm_region_top_info_data_t info;
	mach_port_t               object_name;

        if ( mach_vm_region(task, 
			    &address, 
			    &size, 
			    VM_REGION_TOP_INFO, 
			    (vm_region_info_t)&info, 
			    &count, 
			    &object_name) != KERN_SUCCESS ) // There are no more vm regions to look at.
            break;

	//	printf("Address <%lx>, Size<%lu>\n", address, size);

        if (in_shared_region(cputype, address)) {

            // Check if this process has the globally shared text and data regions mapped in.
            // If so, set has_shared_regions to 1 and so we only check once.
            if (has_shared_regions == 0 && info.share_mode == SM_EMPTY) {
                vm_region_basic_info_data_64_t basic_info;

                count = VM_REGION_BASIC_INFO_COUNT_64;
                if ( mach_vm_region(task, 
				    &address, 
				    &size, 
				    VM_REGION_BASIC_INFO, 
				    (vm_region_info_t)&basic_info, 
				    &count, &object_name) != KERN_SUCCESS ) 
                    break;

                if (basic_info.reserved) 
                    has_shared_regions = 1;

            }

            // Skip the vm region if it is not a shared private region.
            if (info.share_mode != SM_PRIVATE) 
                continue;

        }

        if (info.share_mode == SM_EMPTY) 
            empty += size;

    }

    // Subtract out the globally shared text and data region.
    if (has_shared_regions == 1) 
        vprvt -= shared_region_size(cputype);

    // Subtract out the empty pages (pagezero, stack guard, etc)
    vprvt -= empty;

    return 0;
}

int
GetPerformanceInfo ( PerfBlock * pb )
{

    int rc = OSXMemoryUsage ( pb->resPrivate,
			      pb->virPrivate,
			      pb->virSize );
    if (rc) return rc;

    rusage udata;
    rc = getrusage( RUSAGE_SELF, &udata );
    if (rc) return rc;

    pb->userTime       = udata.ru_utime;
    pb->sysTime        = udata.ru_stime;

    // The following are copied but not actually populated as in OS/X 10.5
    pb->minorFaults    = udata.ru_minflt;
    pb->majorFaults    = udata.ru_majflt;
    pb->volContextSw   = udata.ru_nvcsw;
    pb->involContextSw = udata.ru_nivcsw;

    return 0;

}

#endif
